package com.ddw.meedeeuh.presenters;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;

import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import com.ddw.meedeeuh.MeedeeuhApp;
import com.ddw.meedeeuh.shared.events.DBMovieSelectedEvent;
import com.ddw.meedeeuh.shared.events.DBMovieSelectedEventHandler;
import com.ddw.meedeeuh.shared.events.OpenMovieEditorEvent;
import com.ddw.meedeeuh.shared.io.SourceFile;
import com.ddw.meedeeuh.shared.movie.EditableMovie;
import com.ddw.meedeeuh.shared.movie.database.DBMovie;
import com.ddw.meedeeuh.shared.movie.local.NfoTransformer;
import com.ddw.meedeeuh.shared.movie.local.NfoTransformerFactory;
import com.ddw.meedeeuh.shared.mvp.PresenterBase;
import com.ddw.meedeeuh.shared.mvp.View;
import com.ddw.meedeeuh.views.MovieEditorViewImpl;

public class MovieEditorPresenter extends PresenterBase<MovieEditorPresenter.MovieEditorView> {

	private final Map<Integer, MovieEditorView> openViews = new HashMap<Integer, MovieEditorView>();

	public MovieEditorPresenter(MeedeeuhApp app, MovieEditorView view) {
		super(app, view);
	}

	public static interface MovieEditorView extends View, EditableMovie {

		public MovieEditorTabType getSelectedTab();

		public void addTabChangeListener(ChangeListener listener);

		public void setNfoText(String nfo);

		public void setNewNfoText(String nfo);

	}

	public static enum MovieEditorTabType {
		INFO,
		MEDIA,
		POSTER,
		FANART,
		EXTRATHUMBS,
		NFO
	}

	@Override
	public void onSetup() {
		// Listen for movie selection
		getApp().getEventBus().register(new DBMovieSelectedEventHandler() {

			@Override
			public void onDBMovieSelected(DBMovieSelectedEvent e) {
				// Setup or get existing view
				final MovieEditorView view;
				if(openViews.containsKey(e.getMovie().getMovieId()) == false) {
					view = new MovieEditorViewImpl();
					onSetup(e.getMovie(), view);
					openViews.put(e.getMovie().getMovieId(), view);
				} else {
					view = openViews.get(e.getMovie().getMovieId());
				}

				// Open the view
				getApp().getEventBus().post(new OpenMovieEditorEvent(view));
			}

		});
	}

	private void onSetup(final DBMovie movie, final MovieEditorView editor) {
		onInfoTabSelected(editor, movie, true);

		editor.addTabChangeListener(new ChangeListener() {

			private boolean infoFirstTime = true;
			private boolean mediaFirstTime = true;
			private boolean nfoFirstTime = true;
			private boolean posterFirstTime = true;
			private boolean fanartFirstTime = true;
			private boolean extrathumbsFirstTime = true;

			@Override
			public void stateChanged(ChangeEvent e) {
				final MovieEditorTabType tab = editor.getSelectedTab();

				switch(tab) {
					case INFO:
						onInfoTabSelected(editor, movie, infoFirstTime);
						infoFirstTime = false;
						break;
					case MEDIA:
						onMediaTabSelected(editor, movie, mediaFirstTime);
						mediaFirstTime = false;
						break;
					case NFO:
						onNfoTabSelected(editor, movie, nfoFirstTime);
						nfoFirstTime = false;
						break;
					case POSTER:
						onPosterTabSelected(editor, movie, posterFirstTime);
						posterFirstTime = false;
						break;
					case EXTRATHUMBS:
						onExtraThumbsTabSelected(editor, movie, extrathumbsFirstTime);
						extrathumbsFirstTime = false;
						break;
					case FANART:
						onFanartTabSelected(editor, movie, fanartFirstTime);
						fanartFirstTime = false;
						break;
					default:
						// Do nothing
				}
			}

		});
	}

	private void onInfoTabSelected(MovieEditorView editor, DBMovie movie, boolean firstTime) {
		if(firstTime) {
			editor.setCredits(movie.getCredits());
			editor.setDirector(movie.getDirector());
			editor.setFanart(movie.getFanart());
			editor.setGenre(movie.getGenre());
			editor.setImdbId(movie.getImdbId());
			editor.setMpaa(movie.getMpaa());
			editor.setName(movie.getName());
			editor.setNfo(movie.getNfo());
			editor.setOriginalTitle(movie.getOriginalTitle());
			editor.setOutline(movie.getOutline());
			editor.setPlayCount(movie.getPlayCount());
			editor.setPlot(movie.getPlot());
			editor.setPoster(movie.getPoster());
			editor.setRating(movie.getRating());
			editor.setRuntime(movie.getRuntime());
			editor.setSet(movie.getSet());
			editor.setSortTitle(movie.getSortTitle());
			editor.setTagLine(movie.getTagLine());
			editor.setThumbnail(movie.getThumbnail());
			editor.setTop250(movie.isTop250());
			editor.setTrailer(movie.getTrailer());
			editor.setVotes(movie.getVotes());
			editor.setWatched(movie.isWatched());
			editor.setYear(movie.getYear());
			// TODO: Actors
		}
	}

	private void onMediaTabSelected(MovieEditorView editor, DBMovie movie, boolean firstTime) {
		// TODO: Stream details
	}

	private void onNfoTabSelected(MovieEditorView editor, DBMovie movie, boolean firstTime) {
		if(firstTime) {
			editor.setNfoText("Loading...");
			try {
				final NfoLoaderWorker worker = new NfoLoaderWorker(movie.getNfo(), editor);
				worker.execute();
			} catch(Throwable e) {
				e.printStackTrace();
				editor.setNfoText("Failed to get nfo");
			}
		}

		final NfoTransformer transformer = NfoTransformerFactory.create();
		try {
			editor.setNewNfoText(transformer.transform(movie, getApp().getMovieSettings()));
		} catch (Throwable e) {
			e.printStackTrace();
			editor.setNewNfoText("Failed to create nfo");
		}
	}

	private void onPosterTabSelected(MovieEditorView editor, DBMovie movie, boolean firstTime) {
		// TODO poster
	}

	private void onFanartTabSelected(MovieEditorView editor, DBMovie movie, boolean firstTime) {
		// TODO fanart
	}

	private void onExtraThumbsTabSelected(MovieEditorView editor, DBMovie movie, boolean firstTime) {
		// TODO extrathumbs
	}

	private static class NfoLoaderWorker extends SwingWorker<String, Void> {

		private final SourceFile nfo;
		private final MovieEditorView editor;

		public NfoLoaderWorker(SourceFile nfo, MovieEditorView editor) {
			this.nfo = nfo;
			this.editor = editor;
		}

		@Override
		protected String doInBackground() throws Exception {
			if(this.nfo.getInstance().exists() == false) {
				throw new RuntimeException("Nfo does not exist");
			}

			// Read the nfo file
			final String content = readNfo();

			// Attempt to parse and format the nfo
			try {
				final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
				final DocumentBuilder db = dbf.newDocumentBuilder();
				final InputSource sourceXml = new InputSource(new StringReader(content.toString()));
				final Document doc = db.parse(sourceXml);

				final TransformerFactory tf = TransformerFactory.newInstance();
				tf.setAttribute("indent-number", new Integer(4));

				final Transformer transformer = tf.newTransformer();
				transformer.setOutputProperty(OutputKeys.INDENT, "yes");
				final StreamResult result = new StreamResult(new StringWriter());
				final DOMSource source = new DOMSource(doc);
				transformer.transform(source, result);

				return result.getWriter().toString();
			} catch (Throwable e) {
				System.out.println(e);
			}

			// Fallback to unparsed text if failed
			return content;
		}

		private String readNfo() throws FileNotFoundException, IOException {
			FileReader reader = null;
			final StringBuilder content = new StringBuilder();

			try {
				reader = new FileReader(this.nfo.getInstance());

				BufferedReader buffer = null;

				try {
					buffer = new BufferedReader(reader);

					String read;
					while((read = buffer.readLine()) != null) {
						content.append(read);
					}
				} catch (IOException e) {
					throw e;
				} finally {
					if(buffer != null) {
						buffer.close();
					}
				}
			} catch (FileNotFoundException e) {
				throw e;
			} finally {
				if(reader != null) {
					reader.close();
				}
			}

			return content.toString();
		}

		@Override
		protected void done() {
			if(getState() == StateValue.DONE) {
				try {
					this.editor.setNfoText(get());
				} catch (InterruptedException e) {
					this.editor.setNfoText("Failed to load nfo!");
				} catch (ExecutionException e) {
					this.editor.setNfoText("Failed to load nfo!");
				}
			}
		}

	}

}
